#include "fun_file.h"
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#endif

int is_file_exists(char *path) {  
    return (access(path, F_OK) == 0);  
} 

int get_file_size(char *path)
{
    int len;
    FILE *fp = fopen(path, "r");
    if(fp == NULL) {
		perror("fopen");
		return -1;
	}
    fseek(fp, 0L, SEEK_END );
    len = ftell(fp);
    fclose(fp);
    return len;
}

/**
 * 判断路径是否为目录
 * @return 
 *   1 = 是目录
 *   0 = 不是目录或路径无效
 *  -1 = 系统级错误（如权限不足）
 */
int is_directory(const char *path) {
#ifdef _WIN32
    DWORD attrib = GetFileAttributesA(path);
    if (attrib == INVALID_FILE_ATTRIBUTES) {
        DWORD err = GetLastError();
        // 明确区分路径不存在和其他错误
        if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
            return -1; // 路径不存在
        }
        return -1;    // 其他系统错误
    }
    return (attrib & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
#else
    struct stat statbuf;
    if (stat(path, &statbuf) != 0) {
        if (errno == ENOENT) {
            return -1; // 路径不存在
        }
        return -1;    // 其他系统错误
    }
    return S_ISDIR(statbuf.st_mode) ? 1 : 0;
#endif
}

static struct file_list_s *malloc_file_list() {
    struct file_list_s *list = malloc(sizeof(struct file_list_s));
    if (list) {
        list->fileName[0] = 0;
        list->next = NULL;
    }
    return list;
}

// 列出目录下的所有文件
struct file_list_s * get_list_files(const char *path, char *suffix) {
    struct file_list_s *list = NULL,*fir;
#ifdef _WIN32
    char search_path[MAX_PATH];
    snprintf(search_path, MAX_PATH, "%s\\*", path);

    WIN32_FIND_DATAA find_data;
    HANDLE hFind = FindFirstFileA(search_path, &find_data);
    
    if (hFind == INVALID_HANDLE_VALUE) {
        perror("FindFirstFile failed");
        return NULL;
    }

    do {
        if (strcmp(find_data.cFileName, ".") == 0 || 
            strcmp(find_data.cFileName, "..") == 0) {
            continue;
        }

        fir = malloc_file_list();
        if (!fir) break;
        // 获取文件名的扩展名
        if (suffix) {
            char *ext = strrchr(find_data.cFileName, '.');
            if (ext && strcmp(ext, suffix) == 0) {
                strncpy(fir->fileName, find_data.cFileName, sizeof(fir->fileName));
            }
        } else {
            strncpy(fir->fileName, find_data.cFileName, sizeof(fir->fileName));
        }
        fir->next = list;
        list = fir;

    } while (FindNextFileA(hFind, &find_data) != 0);

    FindClose(hFind);

#else
    DIR *dir = opendir(path);
    if (!dir) {
        perror("opendir failed");
        return NULL;
    }

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(".", entry->d_name)==0 || strcmp("..", entry->d_name)==0) {
            continue;   // 最好排除下 . 跟 .. 的目录
        }

        fir = malloc_file_list();
        if (!fir) break;    // 空间不足
        if (suffix) {
            // 获取文件名的扩展名
            const char *ext = strrchr(entry->d_name, '.');
            if (ext && strcmp(ext, suffix) == 0) {
                printf("%s\n", entry->d_name);
                strncpy(fir->fileName, entry->d_name, sizeof(fir->fileName));
            }
        } else {
            strncpy(fir->fileName, entry->d_name, sizeof(fir->fileName));
        }
        fir->next = list;
        list = fir;

        #ifdef _GNU_SOURCE
        if (entry->d_type == DT_REG) { // 普通文件

        } else if (entry->d_type == DT_DIR) { // 目录
            
        } else if (entry->d_type == DT_CHR) { // 字符设备

        } else if (entry->d_type == DT_BLK) { // 块设备

        } else if (entry->d_type == DT_LNK) { // 符号链接

        }
        #endif
    }

    closedir(dir);
#endif

    return list;
}

void get_list_files_free(struct file_list_s *list) {
    if (!list) return;
    get_list_files_free(list->next);
    free(list);
}

// 返回0成功
int mkdir_rd(char *dir, int allow_recursive) {
    if (!allow_recursive) {
        if (access(dir, F_OK) != 0) {
            #ifdef _WIN32
            if (mkdir(dir) != 0)
            #else 
            if (mkdir(dir, 0755) != 0)
            #endif
            {
                perror("mkdir failed");
                return -1;
            }
        }
        return 0;
    }

    char *temp = malloc(strlen(dir)+1);
    if (temp == NULL) {
        perror("strdup failed");
        return -1;
    }
    strcpy(temp, dir);
    char dir_path[1024] = {0};
    const char *separator = "/";
    int is_absolute = (dir[0] == '/');

    char *current = strtok(temp, separator);
    while (current != NULL) {
        if (is_absolute && dir_path[0] == '\0') {
            // 处理绝对路径的开头
            snprintf(dir_path, sizeof(dir_path), "/%s", current);
            is_absolute = 0; // 已处理
        } else {
            if (dir_path[0] != '\0') {
                strncat(dir_path, separator, sizeof(dir_path) - strlen(dir_path) - 1);  // 字符串拼接
            }
            strncat(dir_path, current, sizeof(dir_path) - strlen(dir_path) - 1);
        }

        // 检查并创建目录
        if (access(dir_path, F_OK) != 0) {
            #ifdef _WIN32
            if (mkdir(dir_path) != 0)
            #else 
            if (mkdir(dir_path, 0755) != 0)
            #endif
            {
                perror("mkdir failed");
                free(temp);
                return -1;
            }
        }

        current = strtok(NULL, separator);
    }

    free(temp);
    return 0;
}

int rmdir_rd(char *dir) {
    return rmdir(dir);
}

int remove_rd(char *filename) {
    return remove(filename);
}

#if 0

/**
 * 文件编码转换
*/
#include <iconv.h>  // 转换库
static int file_code_convert()
{
    int ret;
    int read_fd = open("utf8.txt",O_RDONLY|O_CREAT);
    int write_fd= open("gbk.txt", O_RDWR|O_CREAT,0666); //建文件时会修改权限
    char read_buf[1024] = {0};
    size_t inLen ,outLen=1024 ;
    char *outbuf = (char*)malloc(1024);
    char *inbuf  = (char*)malloc(1024);     //这两块空间必须malloc，不可以用数组
    char *ob = outbuf;
    char *ib = inbuf;       //用来记录上面两块空间的地址

    inLen = read(read_fd, read_buf,1024);
    memcpy(inbuf,read_buf,inLen);

    /*
        获取转换的句柄
        tocode:    转换后的类型
        fromcode:  转换前的类型
    */
    iconv_t icv = iconv_open("GBK","UTF-8");
    if(*(int*)icv == -1)
    {
        perror("iconv_open");
        return -1;
    }

    /*
        开始转换
        cd      句柄
        inbuf   需要转换的内容
        inbytesleft 最多转换的字节数
        outbuf  转换后的内容
        outbytesleft 转换后最多存放在outbuf中的字节数
        返回值 成功返回0吧
        备注：完成后
            inbytesleft 会返回转换的字节数
            inbuf 会指向偏移 inbuf 字节的长度的地址
            outbytesleft 会返回转换后的长度
            outbuf 也同样会指向偏移 outbytesleft 字节的长度的地址
            所以直接读inbuf和outbuf是读不出转化后内容
    */
    ret = iconv(icv, &inbuf, &inLen ,&outbuf ,&outLen);
    if(ret == -1)
    {
        perror("iconv");
        return -1;
    }

    /*关闭*/
    iconv_close(icv);

    write(write_fd,ob,1024-outLen);
    close(write_fd);
    close(read_fd);
    free(ob);
    free(ib);
    return 0;
}

#include <sys/stat.h>
#include <sys/types.h>
static void sample() 
{
    // struct stat 用于存储关于文件或文件系统的状态信息。
    // 当你想要查询一个文件或目录的元数据（如大小、创建时间、修改时间等）时，你可以使用stat()函数
    char *file_path = "/tmp";
    struct stat sta;
    if (stat(file_path, &sta) == 0) {    // 可以用于判断文件或文件夹是否存在
        printf("file or folder exist");
        if (S_ISREG(sta.st_mode)) {     // 常规文件
            
        } else if (S_ISDIR(sta.st_mode)) {  // 目录

        } else if (S_ISLNK(sta.st_mode)) {  // 连接

        } else if (S_ISCHR(sta.st_mode)) {  // 字符设备

        } else if (S_ISBLK(sta.st_mode)) {  // 块设备

        } else if (S_ISFIFO(sta.st_mode)) {  // FIFO文件

        } else if (S_ISSOCK(sta.st_mode)) {  // SOCKET文件

        }
    } else {
        printf("file or folder do not exist");
    }
}

/**
 * @brief 文件读写操作参考 1
 * 
 * @note 头文件 #include <stdio.h>
 * 
 * fopen() 是 C 标准库函数，提供了更高级别的接口，包括缓冲和文件位置管理。
 * 由于 fopen() 是库函数，所以它在所有支持 C 标准库的系统上都可用，包括非 Unix 系统。
 */
#include <stdio.h>
static void file_read_write_demo_1(void) {
    /*
        fopen 第二个参数：
        r   以只读方式打开文件，该文件必须存在。
        r+  以读/写方式打开文件，该文件必须存在。
        rb+ 以读/写方式打开一个二进制文件，只允许读/写数据。
        rt+ 以读/写方式打开一个文本文件，允许读和写。
        w   打开只写文件，若文件存在则文件长度清为零，即该文件内容会消失；若文件不存在则创建该文件。
        w+  打开可读/写文件，若文件存在则文件长度清为零，即该文件内容会消失；若文件不存在则创建该文件。
        a   以附加的方式打开只写文件。若文件不存在，则会创建该文件；如果文件存在，则写入的数据会被加到文件尾后，即文件原先的内容会被保留（EOF 符保留）。
        a+  以附加方式打开可读/写的文件。若文件不存在，则会创建该文件，如果文件存在，则写入的数据会被加到文件尾后，即文件原先的内容会被保留（EOF符不保留）。
        wb  以只写方式打开或新建一个二进制文件，只允许写数据。
        wb+ 以读/写方式打开或新建一个二进制文件，允许读和写。
        wt+ 以读/写方式打开或新建一个文本文件，允许读和写。
        at+ 以读/写方式打开一个文本文件，允许读或在文本末追加数据。
        ab+ 以读/写方式打开一个二进制文件，允许读或在文件末追加数据。
    */
    FILE *fp = fopen("/tmp/1.txt", "a+");
    if(fp == NULL) {
		perror("fopen");
		return ;
	}
    char *fop_buff[1024];
    // fread跟fwrite的第二个参数为项大小（单位：字节），第三个为写入项数量
    int len = fread(fop_buff, 1, sizeof(fop_buff), fp);
    len = fwrite("hello", 1, sizeof("hello"), fp);
    fclose(fp);
}

/**
 * @brief 文件读写操作参考 2
 * 
 * @note 头文件  #include <fcntl.h>
 *              #include <sys/types.h>
 *              #include <sys/stat.h>
 * 
 * open() 是一个系统调用，直接由操作系统内核提供。它提供了非常低级的文件访问，
 * 并且在所有 Unix 和类 Unix 系统（包括 Linux）上可用。
 */
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
static void file_read_write_demo_2(void) {
    /*
        open第二个参数flags（位掩码，用于决定文件的访问模式和行为）：
        O_RDONLY：只读打开。
        O_WRONLY：只写打开。
        O_RDWR：读写打开。
        O_CREAT：如果文件不存在，就创建新文件。
        O_TRUNC：如果文件已存在，并且是以写入(O_WRONLY 或 O_RDWR)方式打开，则将其长度截断为0。
        O_APPEND：在每次写入时，都从文件末尾开始添加。
        O_NOCTTY: 如果路径名指向终端设备，不要把这个设备用作控制终端。
        O_NONBLOCK: 如果路径名指向FIFO/块文件/字符文件，则把文件的打开和后继I/O

        open第三个参数mode（当使用 O_CREAT 标志时，该参数用于指定新文件的权限）：
        S_IRWXU,    700权限，代表该文件所有者具有可读、可写及可执行的权限。
        S_IRUSR或S_IREAD,  400权限，代表该文件所有者具有可读取的权限。
        S_IWUSR或S_IWRITE, 200权限，代表该文件所有者具有可写入的权限。
        S_IXUSR或S_IEXEC,  100权限，代表该文件所有者具有可执行的权限。

        S_IRWXG,  070权限，代表该文件用户组具有可读、可写及可执行的权限。
        S_IRGRP,  040权限，代表该文件用户组具有可读的权限。
        S_IWGRP,  020权限，代表该文件用户组具有可写入的权限。
        S_IXGRP,  010权限，代表该文件用户组具有可执行的权限。

        S_IRWX,   O07权限，代表其他用户具有可读、可写及可执行的权限。
        S_IROTH,  004权限，代表其他用户具有可读的权限
        S_IWOTH,  002权限，代表其他用户具有可写入的权限。
        S_IXOTH,  001权限，代表其他用户具有可执行的权限。
    */
    int fd = open("/tmp/1.txt", O_RDWR | O_CREAT, S_IRWXU);
    if (fd < 0) {
        perror("open");
		return ;
    }
    char buff[1024];
    int len = read(fd, buff, sizeof(buff));
    len = write(fd, buff, 100);
    close(fd);
}

#endif